﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Linq;

namespace BattleforgeAssault
{
    public class Game : GameObject
    {
        #region Properties

        private Action _currentAction = null;
        public Action CurrentAction { get { return _currentAction; } set { _currentAction = value; RaisePropertyChanged("CurrentAction"); } }

        private CapitalZone _currentAttackedZone = null;
        public CapitalZone CurrentAttackedZone { get { return _currentAttackedZone; } set { _currentAttackedZone = value; RaisePropertyChanged("CurrentAttackedZone"); } }

        private List<Unit> _currentAttackers = new List<Unit>();
        public List<Unit> CurrentAttackers { get { return _currentAttackers; } set { _currentAttackers = value; RaisePropertyChanged("CurrentAttackers"); } }

        private List<Unit> _currentDefenders = new List<Unit>();
        public List<Unit> CurrentDefenders { get { return _currentDefenders; } set { _currentDefenders = value; RaisePropertyChanged("CurrentDefenders"); } }

        private Controller _controller = null;
        public Controller Controller { get { return _controller; } }

        private List<Card> _cards = new List<Card>();
        public List<Card> Cards { get { return _cards; } set { _cards = value; RaisePropertyChanged("Cards"); } }

        private List<Player> _players = new List<Player>();
        public List<Player> Players { get { return _players; } set { _players = value; RaisePropertyChanged("Players"); } }

        private bool _isWaiting = false;
        public bool IsWaiting { private get { return _isWaiting; } set { _isWaiting = value; RaisePropertyChanged("IsWaiting"); } }

        private Types.States _state = Types.States.Setup;
        public Types.States State { get { return _state; } set { _state = value; RaisePropertyChanged("State"); } }

        private Types.Phases _phase = Types.Phases.None;
        public Types.Phases Phase { get { return _phase; } set { _phase = value; RaisePropertyChanged("Phase"); } }

        private Player _activePlayer = null;
        public Player ActivePlayer { get { return _activePlayer; } set { _activePlayer = value; RaisePropertyChanged("ActivePlayer"); } }

        private int _turn = 0;
        public int Turn { get { return _turn; } set { _turn = value; RaisePropertyChanged("Turn"); } }

        private string _statusMessage = "Game Started";
        public string StatusMessage { get { return _statusMessage; } set { _statusMessage = value; RaisePropertyChanged("StatusMessage"); } }

        private Table _table = new Table();
        public Table Table { get { return _table; } set { _table = value; RaisePropertyChanged("Table"); } }

        #endregion Properties

        #region Effects

        private List<Effect> _effects = new List<Effect>();
        public void AddEffect(Effect effect) { _effects.Add(effect); _effects.Sort(); }
        public void RemoveEffect(Effect effect) { _effects.Remove(effect); }
        public void CheckEffects(GameObject sender, string trigger)
        {
            //Messager.SendMessage("Checking {0} effects", trigger);
            foreach (Effect effect in _effects.Where(e => !(e is Modifier)))
            {
                if (effect.Trigger == trigger)
                {
                    if (effect.Applicable(sender))
                    {
                        Messager.SendMessage("Applying {0}'s effect", effect.Source);
                        effect.Apply(sender);
                    }
                    else
                    {
                        Messager.SendMessage("{0}'s effect does not apply", effect.Source);
                    }
                }
            }
        }
        public int GetModified(GameObject sender, string stat, int initialValue)
        {
            //Messager.SendMessage("Checking {0} modifiers", stat);
            int modifiedValue = initialValue;
            foreach (Modifier mod in _effects.Where(e => e is Modifier))
            {
                if (mod.Trigger == stat)
                {
                    if (mod.Applicable(sender))
                    {
                        Messager.SendMessage("{0} modifies {1}'s {2}", mod.Source, sender, stat);
                        modifiedValue = mod.Modify(modifiedValue);
                    }
                    else
                    {
                        Messager.SendMessage("{0}'s modifier does not apply to {1}", mod.Source, sender);
                    }
                }
            }
            return modifiedValue;
        }

        #endregion Effects

        public Game(Controller controller)
        {
            _controller = controller;
        }

        #region Game Phases

        private void StartOfTurnPhase()
        {
            Phase = Types.Phases.StartOfTurn;
            Messager.SendMessage("Starting turn {0}", Turn);
            CheckEffects(this, "PreStartOfTurnPhase");
            //  a. Actions (by any player)
            StatusMessage = "Play Start of Turn Phase actions";
            ActionWindow();
            CheckEffects(this, "PostStartOfTurnPhase");
        }

        private void KingdomPhase()
        {
            Phase = Types.Phases.Kingdom;
            Messager.SendMessage("Starting Kingdom phase");
            CheckEffects(this, "PreKingdomPhase");
            //  a. AP resets resources
            Messager.SendMessage("Resetting {0}'s resources", ActivePlayer);
            ActivePlayer.Resources = 0;
            //  b. AP may restore 1 corrupt unit
            //ActivePlayer.UncorruptUnit(1);
            //  c. AP gains kingdom power in resources
            int totalKingdomPower = ActivePlayer.Kingdom.Power;
            foreach (var card in ActivePlayer.Kingdom.Cards)
            {
                if (card is Unit) { totalKingdomPower += ((Unit)card).Power; }
                if (card is Support) { totalKingdomPower += ((Support)card).Power; }
            }
            Messager.SendMessage("{0} receives {1} resources", ActivePlayer, totalKingdomPower);
            ActivePlayer.Resources = totalKingdomPower;
            //  d. Actions
            StatusMessage = "Play Kingdom Phase actions";
            ActionWindow();
            CheckEffects(this, "PostKingdomPhase");
        }

        private void QuestPhase()
        {
            Phase = Types.Phases.Quest;
            Messager.SendMessage("Starting Quest phase");
            CheckEffects(this, "PreQuestPhase");
            //  a. AP gains quest power in cards
            int totalQuestPower = ActivePlayer.Quest.Power;
            foreach (var card in ActivePlayer.Quest.Cards)
            {
                if (card is Unit) { totalQuestPower += ((Unit)card).Power; }
                if (card is Support) { totalQuestPower += ((Support)card).Power; }
            }
            Messager.SendMessage("{0} receives {1} cards", ActivePlayer, totalQuestPower);
            ActivePlayer.DrawCards(totalQuestPower);
            //  b. Actions
            StatusMessage = "Play Quest Phase actions";
            ActionWindow();
            CheckEffects(this, "PostQuestPhase");
        }

        private void CapitalPhase()
        {
            Phase = Types.Phases.Capital;
            Messager.SendMessage("Starting Capital phase");
            CheckEffects(this, "PreCapitalPhase");
            //  a. AP may play cards (including 1 development)
            //  b. Action (any time)
            StatusMessage = "Play Capital Phase actions and cards";
            PlayWindow(); // includes an ActionWindow for other players
            CheckEffects(this, "PostCapitalPhase");
        }

        private void BattlefieldPhase()
        {
            Phase = Types.Phases.Battlefield;
            Messager.SendMessage("Starting Battlefield phase");
            CheckEffects(this, "PreBattlfieldPhase");
            StatusMessage = "Attack";

            CurrentAttackedZone = null;
            CurrentAttackers.Clear();
            CurrentDefenders.Clear();

            //  a. AP may declare attack zone
            if (DeclareAttackZone())
            {
                //   c. AP may declare attackers
                DeclareAttackers();
                //   e. DP may declare defenders
                DeclareDefenders();
                //   g. Damage is assigned
                AssignCombatDamage();

                //   i. Damage is applied
                //   j. Units with damage >= HP leave play
                //   k. Burn tokens are placed
                ApplyAllAssignedDamage();
            }
            //  l. Actions (even if AP did not attack)
            FinishBattlefieldPhase();
        }

        private bool DeclareAttackZone()
        {
            var targets = ActivePlayer.SelectTargets("Choose attack zone or pass", 0, 1, (t) =>
            {
                return (t is CapitalZone && ((CapitalZone)t).Owner != ActivePlayer);
            }); // TODO: show this in the UI

            if (targets != null && targets.Count > 0 && targets[0] is CapitalZone)
            {
                CurrentAttackedZone = targets[0] as CapitalZone;
                //   b. Actions
                ActionWindow(ActivePlayer, "Respond to zone selection");
                return true;
            }

            return false;
        }

        private void DeclareAttackers()
        {
            var targets = ActivePlayer.SelectTargets("Choose attackers or pass", 0, 0, (t) =>
            {
                if (t is Unit && ((Unit)t).Controller == ActivePlayer)
                {
                    if (((Unit)t).CanAttackAnywhere ||
                        ((Unit)t).MustAttack ||
                        ((Unit)t).Location is BattlefieldZone)
                    {
                        return true;
                    }
                }
                return false;
            }); // TODO: show this in the UI

            if (targets != null && targets.Count > 0)
            {
                foreach (var target in targets) { if (target is Unit) CurrentAttackers.Add(target as Unit); }
                //   d. Actions
                ActionWindow(ActivePlayer, "Respond to attacker selection");
            }
            else
            {
                ActionWindow(ActivePlayer, "Respond to no attackers");
            }
        }

        private void DeclareDefenders()
        {
            var targets = CurrentAttackedZone.Owner.SelectTargets("Choose defenders or pass", 0, 0, (t) =>
            {
                if (t is Unit && ((Unit)t).Controller == ActivePlayer)
                {
                    if (((Unit)t).CanDefendAnywhere ||
                        ((Unit)t).MustDefend ||
                        ((Unit)t).Location == CurrentAttackedZone)
                    {
                        return true;
                    }
                }
                return false;
            }); // TODO: show this in the UI

            if (targets != null && targets.Count > 0)
            {
                foreach (var target in targets) { if (target is Unit) CurrentDefenders.Add(target as Unit); }
                //   f. Actions
                ActionWindow(ActivePlayer, "Respond to defender selection");
            }
            else
            {
                ActionWindow(ActivePlayer, "Respond to no defenders");
            }
        }

        private void AssignCombatDamage()
        {
            int totalDamage = 0;
            foreach (var attacker in CurrentAttackers)
            {
                totalDamage += attacker.Power;
            }

            // assign each damage point one at a time for now TODO: fix this
            while (totalDamage > 0)
            {
                var targets = ActivePlayer.SelectTargets("Choose damage targets", 1, totalDamage, (t) =>
                {
                    if (t is Unit && CurrentDefenders.Contains(((Unit)t))) return true;
                    // TODO: disallow targeting a capital until units are gone
                    if (t is Location && ((Location)t) == CurrentAttackedZone) return true;
                    // TODO: allow split damage better
                    return false;
                });

                if (targets != null && targets.Count > 0)
                {
                    foreach (var target in targets)
                    {
                        if (target is Unit) { ((Unit)target).DamageAssigned += 1; totalDamage--; }
                        if (target is CapitalZone) { ((CapitalZone)target).DamageAssigned += 1; totalDamage--; }
                    }
                }
            }
            //   h. Actions
            ActionWindow(ActivePlayer, "Respond to damage assigned");
        }

        private void FinishBattlefieldPhase()
        {
            StatusMessage = "Play Battlefield Phase actions";
            ActionWindow();
            CheckEffects(this, "PostBattlefieldPhase");
        }

        private void EndOfTurnPhase()
        {
            Phase = Types.Phases.EndOfTurn;
            Messager.SendMessage("Ending turn");
            CheckEffects(this, "PreEndOfTurnPhase");
            //  a. The turn ends, proceed to next player
            CheckEffects(this, "PostEndOfTurnPhase");
            SwitchToNextPlayer();
        }

        #endregion Game Phases

        #region Helper Methods

        public void Setup()
        {
            Player gameOwner = new Player(this) { Name = "Player" };
            Players.Add(gameOwner);
            Table.Owner = gameOwner;

            // TODO: ask for number of players, rule set, decks and starting player
            // for now just assume one player and one bot with the player going first
            Player bot = new Bot(this) { Name = "Bot" };
            Players.Add(bot);

            ActivePlayer = gameOwner;
            CreateSampleDeck();
            _controller.View.CreateCardViews();
        }

        public void Begin()
        {
            foreach (var player in Players) { Deal(player); }
            State = Types.States.Playing;
            Loop();
        }

        private void Loop()
        {
            while (State != Types.States.Ended)
            {
                StartOfTurnPhase();
                KingdomPhase();
                QuestPhase();
                CapitalPhase();
                BattlefieldPhase();
                EndOfTurnPhase();
            }
        }

        public void Deal(Player player)
        {
            player.DrawCards(7);
            int answer = player.AnswerQuery(new Query("Would you like a new hand of 7 cards?", "Yes", "No"));
            if (answer == 0) { player.Reset(); player.DrawCards(7); }
        }

        public void WaitForInput()
        {
            IsWaiting = true;
            while (IsWaiting) { Thread.Sleep(100); }
        }

        private void ApplyAllAssignedDamage()
        {
            foreach (var player in Players)
            {
                ApplyAssignedDamage(player.Battlefield.Cards);
                ApplyAssignedDamage(player.Quest.Cards);
                ApplyAssignedDamage(player.Kingdom.Cards);

                ApplyAssignedDamage(player.Battlefield);
                ApplyAssignedDamage(player.Quest);
                ApplyAssignedDamage(player.Kingdom);
            }
        }

        private void ApplyAssignedDamage(CapitalZone zone)
        {
            zone.DamageTaken += zone.DamageAssigned;
            zone.DamageAssigned = 0;
            if (zone.DamageTaken >= (zone.DamageTaken + zone.Developments.Count())) // TODO: should developments add to HPs automatically?
            {
                zone.Burn();
            }
        }

        private void ApplyAssignedDamage(IEnumerable<Card> cards)
        {
            foreach (var card in cards)
            {
                if (card is Unit)
                {
                    Unit unit = card as Unit;
                    unit.DamageTaken += unit.DamageAssigned;
                    unit.DamageAssigned = 0;
                    if (unit.DamageTaken >= unit.HitPoints)
                    {
                        unit.Destroy();
                    }
                }
            }
        }

        private void CreateSampleDeck()
        {
            var Dwarves = new BattleforgeAssault.Cards.DwarfCards();

            foreach (var card in Dwarves)
            {
                Cards.Add(card);
            }

            foreach (var card in Cards)
            {
                card.Owner = ActivePlayer;
                card.Controller = ActivePlayer;
                card.Location = ActivePlayer.Deck;
            }
        }

        private void SwitchToNextPlayer()
        {
            Phase = Types.Phases.None;
            ActivePlayer = GetNextPlayer(ActivePlayer);
            Messager.SendMessage("It is now {0}'s turn", ActivePlayer);
            Turn++;
        }

        private Player GetNextPlayer(Player currentPlayer)
        {
            int playerIndex = Players.IndexOf(currentPlayer);
            int nextPlayerIndex = playerIndex + 1;
            if (nextPlayerIndex >= Players.Count) { nextPlayerIndex = 0; }
            return Players[nextPlayerIndex];
        }

        #endregion Helper Methods

        #region Action Window Methods

        private void ResolveAction(Action action, Player currentPlayer)
        {
            CurrentAction = action;
            if (!action.DeclareTargets()) { return; }
            if (!action.PayCosts()) { return; }
            if (action.Source is Tactic)
            {
                Tactic tactic = action.Source as Tactic;
                currentPlayer.MoveCardTo(tactic, Table);
            }
            ActionWindow(GetNextPlayer(currentPlayer));
            action.Apply(currentPlayer);
            if (action.Source is Tactic)
            {
                Tactic tactic = action.Source as Tactic;
                currentPlayer.MoveCardTo(tactic, currentPlayer.Discard);
            }
        }

        public void ActionWindow()
        {
            ActionWindow(ActivePlayer);
        }
        public void ActionWindow(Player startingPlayer)
        {
            if (CurrentAction == null) { ActionWindow(startingPlayer, "Choose Action or Pass"); }
            else { ActionWindow(startingPlayer, "Choose Response to " + CurrentAction.Source); }
        }
        public void ActionWindow(Player startingPlayer, string message)
        {
            Messager.SendMessage("Action Window started");
            int passCount = 0;
            while (passCount < Players.Count)
            {
                Player currentPlayer = startingPlayer;
                for (int count = 0; count < Players.Count; ++count)
                {
                    Action action = currentPlayer.SelectAction(message);
                    if (action != null)
                    {
                        ResolveAction(action, currentPlayer);
                        passCount = 0;
                    }
                    else { passCount++; }
                    currentPlayer = GetNextPlayer(currentPlayer);
                }
                CurrentAction = null;
            }
        }

        public void PlayWindow() { PlayWindow(ActivePlayer); }
        public void PlayWindow(Player player)
        {
            Messager.SendMessage("Play Window started");
            int passCount = 0;
            while (passCount < Players.Count)
            {
                Player currentPlayer = player;
                for (int count = 0; count < Players.Count; ++count)
                {
                    if (currentPlayer != ActivePlayer)
                    {
                        // the current player can only play actions
                        var action = currentPlayer.SelectAction();
                        if (action != null)
                        {
                            ResolveAction(action, currentPlayer);
                            passCount = 0;
                        }
                        else { passCount++; }
                    }
                    else
                    {
                        // give the current player a full play
                        var card = currentPlayer.SelectCard();
                        if (card != null)
                        {
                            if (card is Tactic)
                            {
                                ResolveAction(card.Action, currentPlayer);
                            }
                            else if (card is Unit || card is Support || card is Quest)
                            {
                                var targets = currentPlayer.SelectTargets("Choose Destination", 1, 1, (t) =>
                                {
                                    if (card.Traits.Contains(Types.Traits.Attachment) && t is Unit && ((Unit)t).Location.IsInPlay) return true;
                                    else if (card is Unit && t is Quest && ((Quest)t).Owner == currentPlayer && ((Quest)t).Location is QuestZone) return true;
                                    else if (t is CapitalZone && ((CapitalZone)t).Owner == currentPlayer)
                                    {
                                        if (card is Quest && !(t is QuestZone)) return false; // TODO: is this true?
                                        if (card.Keywords.Contains(Types.Keywords.BattlefieldOnly) && !(t is BattlefieldZone)) return false;
                                        if (card.Keywords.Contains(Types.Keywords.QuestOnly) && !(t is QuestZone)) return false;
                                        if (card.Keywords.Contains(Types.Keywords.KingdomOnly) && !(t is KingdomZone)) return false;
                                        return true;
                                    }
                                    return false;
                                });

                                if (targets != null && targets.Count > 0)
                                {
                                    if (targets[0] is Location) { currentPlayer.PlayCardTo(card, ((Location)targets[0])); }
                                    if (targets[0] is Quest)
                                    {
                                        var quest = targets[0] as Quest;
                                        currentPlayer.PlayCardTo(card, quest.Location);
                                        quest.Questing = card as Unit;                                        
                                    }
                                    ActionWindow(currentPlayer, "Choose Response to " + card);
                                }
                            }
                            passCount = 0;
                        }
                        else { passCount++; }
                    }
                    currentPlayer = GetNextPlayer(currentPlayer);
                }
                CurrentAction = null;
            }
        }

        #endregion Action Window Methods
    } // class
} // namespace
